This script demonstrates how to use the moveVis R package to create an animation of coyote tracking info. (Scroll to the bottom to see the final result!)

Setup

First load the packages we’ll need:

library(moveVis)
library(move)
library(readr)
library(dplyr)
library(lubridate)


Grab the CSV file:

cous12_fn <- "cous12.csv"; file.exists(cous12_fn)
[1] TRUE


Uncomment the following line if you want to preview the CSV file in a View pane. This can help you see the column names and their data types.

# readr::read_csv(cous12_fn) |> head() |> View()


Import the CSV file as a tibble. We only need 4 columns:

cous12_tbl <- readr::read_csv(cous12_fn, col_select = c(GpsDescription, GPSTime, lon, lat)) 
Rows: 1516 Columns: 4── Column specification ────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (2): GpsDescription, GPSTime
dbl (2): lat, lon
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.


Inspect what we got:

head(cous12_tbl)


Convert the GPSTime column (which is a character object) to a POSIXct column. For this we’ll pick a function from lubridate that matches the date formatting we got.

cous12_dt_tbl <- cous12_tbl |> 
  mutate(gps_dt = dmy_hm(GPSTime))

head(cous12_dt_tbl) 


How many rows and columns?

dim(cous12_dt_tbl)
[1] 1516    5


Duplicate Time Stamps

First we have to deal with duplicate timestamps, which will cause a problem when we convert the data frame to a move object.

How many duplicate time stamps are there?

cous12_dt_tbl |> 
  group_by(gps_dt) |> 
  summarize(num_pts = n()) |> 
  filter(num_pts > 1) |> 
  dim()
[1] 58  2


This tells us there are 58 time stamps that have more than one row. Perhaps there were two locations within the same minute (the GPS time stamps don’t include seconds). Duplicate locations are not that uncommon in many animal tracking datasets. If you want to explore further you could open the CSV file in Excel and examine them, but we will just delete them.

An easy way to get rid of duplicates is by grouping:

cous12_dt_nodups_tbl <- cous12_dt_tbl |> 
  group_by(gps_dt) |> 
  summarize(num_pts = n(), 
            GpsDescription = first(GpsDescription), 
            lon = first(lon), 
            lat = first(lat)) |> 
  ungroup()

head(cous12_dt_nodups_tbl)


Compare the number of rows before and after removing duplicate timestamps:

dim(cous12_dt_tbl)
[1] 1516    5
dim(cous12_dt_nodups_tbl)  
[1] 1458    5

Convert the data frame to a move object

cous12_mov <- moveVis::df2move(cous12_dt_nodups_tbl, 
                               proj = "+init=epsg:4326 +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0", 
                               x = "lon", y = "lat", time = "gps_dt",
                               track_id = "GpsDescription")

We can ignore the warning about the datum. moveVis is still using a rgdal which is the underlying problem.

See what we got:

cous12_mov
class       : Move 
features    : 1458 
extent      : -117.8791, -117.6741, 33.52707, 34.01647  (xmin, xmax, ymin, ymax)
crs         : +proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs 
variables   : 3
names       :          x,           y,       time 
min values  : -117.87915, 33.52706667, 1670955420 
max values  :  -117.6741, 34.01646667, 1672660800 
timestamps  : 2022-12-13 18:17:00 ... 2023-01-02 12:00:00 Time difference of 20 days  (start ... end, duration) 
sensors     : unknown 
indiv. data :  
indiv. value:  
date created: 2022-12-26 19:27:02 
dim(cous12_mov)
[1] 1458    3


Select a time interval for each frame

To make the animation, we’re going to have to select the number of minutes/seconds represented by each frame. We’ll need this when we use the moveVis function that will ‘align’ the dataset to this sampling interval, interpolating locations as needed. This dataset only has the locations for one coyote, but if it contained multiple coyotes their locations would all be interpolated to the same frame interval.

There are always a few gaps in movement datasets, but in general it makes sense to use the dominant sampling interval:

timeLag(cous12_mov, unit = "secs") |> table()

   60   120   180   240   360   420   480   540   600   660   720   780   840   900   960 
    3     1     1     1     3     4     1     9     7     9    10     4     1  1061   297 
 1020  1080  1140  1440  1500  1560  1620  1800  2040  4020  6540 20820 21360 21600 21840 
    6     3     1     2     6     4     1     2     1     1     1     1     1    12     1 
22380 43200 
    1     1 
timeLag(cous12_mov, unit = "secs") |> median()
[1] 900


900 seconds is 15 minutes. Align all the samples to be 900s apart:

cous12_900s_mov <- align_move(cous12_mov, res = 900, unit = "secs")

dim(cous12_900s_mov)
[1] 1895    3


Select a basemap image

moveVis allows you to provide your own basemap image (eg., a GeoTIFF), or you can grab one on-the-fly from Open Street Map or MapBox. For urban coyotes, a satellite image would look nice (see you can see the parks and greenways), but for expediency we’ll use OSM:

get_maptypes(map_service = 'osm')
 [1] "streets"      "streets_de"   "streets_fr"   "humanitarian" "topographic" 
 [6] "roads"        "hydda"        "hydda_base"   "hike"         "grayscale"   
[11] "no_labels"    "watercolor"   "toner"        "toner_bg"     "toner_lite"  
[16] "terrain"      "terrain_bg"   "mtb"         


Create Frames

Creating frames will go a bit faster if we set up parallel processing:

use_multicore(n_cores = NULL, verbose = TRUE)
Number of cores set to be used by moveVis: 11 out of 12


We can also tell it to save the frames to disk (to save on memory)

use_disk(
  frames_to_disk = TRUE,
  dir_frames = paste0(tempdir(), "/moveVis"),
  n_memory_frames = 100,
  verbose = TRUE
)
Disk usage for creating frames enabled.
Directory: 'C:\Users\Andy\AppData\Local\Temp\RtmpotXbwy/moveVis'
Maximum number of frames which will be hold in memory: 100


We create the frames with frames_spatial(). There are lots of arguments in frames_spatial(), and extra functions you can tack on to create the frames. Getting the right look is going to be a matter and trial and error. making frames is pretty fast, but you can use subset_move() to work on just a subset of the locations.

One thing to note is that we don’t specify the frame size or resolution when we’re constructing the frames. Under the hood, each frame is an unrendered ggplot object. We’ll specify the output resolution in the last step when we ‘stitch’ the frames into an animation file.

On my machine generating 1895 frames took ~4 minutes:

frames <- frames_spatial(cous12_900s_mov, path_colours = c("red"),
                       map_service = "osm", map_type = c("streets", "watercolor")[1], alpha = 0.5) |>  
add_labels(x = "Long", y = "Lat") |>  
add_northarrow() |> 
add_scalebar() |>  
add_timestamps(type = "label") %>% add_progress()
Checking temporal alignment...
Processing movement data...
Approximated animation duration: ≈ 75.8s (~1.26 minutes) at 25 fps for 1895 frames
Warning: GDAL Message 1: +init=epsg:XXXX syntax is deprecated. It might return a CRS with a non-EPSG compliant axis order.
Retrieving and compositing basemap imagery...

  |                                                                       | 0 % ~calculating  
  |========                                                               | 10% ~01s          
  |===============                                                        | 20% ~01s          
  |======================                                                 | 30% ~01s          
  |=============================                                          | 40% ~00s          
  |====================================                                   | 50% ~00s          
  |===========================================                            | 60% ~00s          
  |==================================================                     | 70% ~00s          
  |=========================================================              | 80% ~00s          
  |================================================================       | 90% ~00s          
  |=======================================================================| 100% elapsed=01s  
Error in x$.self$finalize() : attempt to apply non-function
Error in (function (x)  : attempt to apply non-function
Assigning raster maps to frames...
Creating frames...

  |                                                                       | 0 % ~calculating  
  |=                                                                      | 1 % ~56s          
  |==                                                                     | 2 % ~56s          
  |===                                                                    | 3 % ~55s          
  |===                                                                    | 4 % ~55s          
  |====                                                                   | 5 % ~54s          
  |=====                                                                  | 6 % ~53s          
  |=====                                                                  | 7 % ~53s          
  |======                                                                 | 8 % ~52s          
  |=======                                                                | 9 % ~52s          
  |========                                                               | 10% ~51s          
  |========                                                               | 11% ~51s          
  |=========                                                              | 12% ~50s          
  |==========                                                             | 13% ~50s          
  |==========                                                             | 14% ~50s          
  |===========                                                            | 15% ~49s          
  |============                                                           | 16% ~49s          
  |=============                                                          | 17% ~48s          
  |=============                                                          | 18% ~48s          
  |==============                                                         | 19% ~47s          
  |===============                                                        | 20% ~48s          
  |===============                                                        | 21% ~47s          
  |================                                                       | 22% ~47s          
  |=================                                                      | 23% ~46s          
  |==================                                                     | 24% ~45s          
  |==================                                                     | 25% ~45s          
  |===================                                                    | 26% ~44s          
  |====================                                                   | 27% ~44s          
  |====================                                                   | 28% ~43s          
  |=====================                                                  | 29% ~42s          
  |======================                                                 | 30% ~42s          
  |=======================                                                | 31% ~41s          
  |=======================                                                | 32% ~40s          
  |========================                                               | 33% ~40s          
  |=========================                                              | 34% ~39s          
  |=========================                                              | 35% ~39s          
  |==========================                                             | 36% ~38s          
  |===========================                                            | 37% ~37s          
  |===========================                                            | 38% ~37s          
  |============================                                           | 39% ~36s          
  |=============================                                          | 40% ~36s          
  |==============================                                         | 41% ~35s          
  |==============================                                         | 42% ~34s          
  |===============================                                        | 43% ~34s          
  |================================                                       | 44% ~33s          
  |================================                                       | 45% ~33s          
  |=================================                                      | 46% ~32s          
  |==================================                                     | 47% ~31s          
  |===================================                                    | 48% ~31s          
  |===================================                                    | 49% ~30s          
  |====================================                                   | 50% ~30s          
  |=====================================                                  | 51% ~29s          
  |=====================================                                  | 52% ~29s          
  |======================================                                 | 53% ~28s          
  |=======================================                                | 54% ~27s          
  |========================================                               | 55% ~27s          
  |========================================                               | 56% ~26s          
  |=========================================                              | 57% ~26s          
  |==========================================                             | 58% ~25s          
  |==========================================                             | 59% ~25s          
  |===========================================                            | 60% ~24s          
  |============================================                           | 61% ~23s          
  |=============================================                          | 62% ~23s          
  |=============================================                          | 63% ~22s          
  |==============================================                         | 64% ~22s          
  |===============================================                        | 65% ~21s          
  |===============================================                        | 66% ~20s          
  |================================================                       | 67% ~20s          
  |=================================================                      | 68% ~19s          
  |=================================================                      | 69% ~19s          
  |==================================================                     | 70% ~18s          
  |===================================================                    | 71% ~17s          
  |====================================================                   | 72% ~17s          
  |====================================================                   | 73% ~16s          
  |=====================================================                  | 74% ~16s          
  |======================================================                 | 75% ~15s          
  |======================================================                 | 76% ~14s          
  |=======================================================                | 77% ~14s          
  |========================================================               | 78% ~13s          
  |=========================================================              | 79% ~13s          
  |=========================================================              | 80% ~12s          
  |==========================================================             | 81% ~11s          
  |===========================================================            | 82% ~11s          
  |===========================================================            | 83% ~10s          
  |============================================================           | 84% ~10s          
  |=============================================================          | 85% ~09s          
  |==============================================================         | 86% ~09s          
  |==============================================================         | 87% ~08s          
  |===============================================================        | 88% ~07s          
  |================================================================       | 89% ~07s          
  |================================================================       | 90% ~06s          
  |=================================================================      | 91% ~05s          
  |==================================================================     | 92% ~05s          
  |===================================================================    | 93% ~04s          
  |===================================================================    | 94% ~04s          
  |====================================================================   | 95% ~03s          
  |=====================================================================  | 96% ~02s          
  |=====================================================================  | 97% ~02s          
  |====================================================================== | 98% ~01s          
  |=======================================================================| 99% ~01s          
  |=======================================================================| 100% elapsed=01m 01s

frames is a list. Let’s see how many frames it made:

length(frames)
[1] 1895


Each element of frames is a ggplot object:

class(frames[[100]])
[1] "gg"     "ggplot"


Let’s preview one of them:


Create the animation

The final step is to ‘stitch’ all the frames together into an animation. Under the hood, moveVis uses ffmpeg. There are a number of output file formats supported. I would recommend animated gifs (which work reliably in PowerPoint, Google Slides, and HTML pages) or mp4.

This is probably the longest step in the process. On my laptop, this step took ~30 minutes to make a GIF, and 27 minutes to make a mp4. You can’t really speed it up because video encoding in general can not be parallelized.

animate_frames(frames, out_file = "cous12_v1.gif",
               fps = 25,
               width = 700,
               height = 700,
               res = 100,
               overwrite = FALSE)
Error: Defined output file already exists and overwriting is disabled.


What did we get?

END


LS0tDQp0aXRsZTogIkNveW90ZSBBbmltYXRpb24gRGVtbyINCmF1dGhvcjogIkFuZHkgTHlvbnMiDQpkYXRlOiAiSmFudWFyeSwgMjAyMyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNClRoaXMgc2NyaXB0IGRlbW9uc3RyYXRlcyBob3cgdG8gdXNlIHRoZSBbbW92ZVZpc10oaHR0cHM6Ly9tb3ZldmlzLm9yZy8pIFINCnBhY2thZ2UgdG8gY3JlYXRlIGFuIGFuaW1hdGlvbiBvZiBjb3lvdGUgdHJhY2tpbmcgaW5mby4gKFNjcm9sbCB0byB0aGUgYm90dG9tIHRvDQpzZWUgdGhlIGZpbmFsIHJlc3VsdCEpDQoNCiMjIFNldHVwDQoNCkZpcnN0IGxvYWQgdGhlIHBhY2thZ2VzIHdlJ2xsIG5lZWQ6DQoNCmBgYHtyfQ0KbGlicmFyeShtb3ZlVmlzKQ0KbGlicmFyeShtb3ZlKQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmBgYA0KDQpcDQoNCkdyYWIgdGhlIENTViBmaWxlOg0KDQpgYGB7cn0NCmNvdXMxMl9mbiA8LSAiY291czEyLmNzdiI7IGZpbGUuZXhpc3RzKGNvdXMxMl9mbikNCmBgYA0KDQpcDQoNClVuY29tbWVudCB0aGUgZm9sbG93aW5nIGxpbmUgaWYgeW91IHdhbnQgdG8gcHJldmlldyB0aGUgQ1NWIGZpbGUgaW4gYSBWaWV3IHBhbmUuDQpUaGlzIGNhbiBoZWxwIHlvdSBzZWUgdGhlIGNvbHVtbiBuYW1lcyBhbmQgdGhlaXIgZGF0YSB0eXBlcy4NCg0KYGBge3J9DQojIHJlYWRyOjpyZWFkX2Nzdihjb3VzMTJfZm4pIHw+IGhlYWQoKSB8PiBWaWV3KCkNCmBgYA0KDQpcDQoNCkltcG9ydCB0aGUgQ1NWIGZpbGUgYXMgYSB0aWJibGUuIFdlIG9ubHkgbmVlZCA0IGNvbHVtbnM6DQoNCmBgYHtyfQ0KY291czEyX3RibCA8LSByZWFkcjo6cmVhZF9jc3YoY291czEyX2ZuLCBjb2xfc2VsZWN0ID0gYyhHcHNEZXNjcmlwdGlvbiwgR1BTVGltZSwgbG9uLCBsYXQpKSANCmBgYA0KDQpcDQoNCkluc3BlY3Qgd2hhdCB3ZSBnb3Q6DQoNCmBgYHtyfQ0KaGVhZChjb3VzMTJfdGJsKQ0KYGBgDQoNClwNCg0KQ29udmVydCB0aGUgR1BTVGltZSBjb2x1bW4gKHdoaWNoIGlzIGEgY2hhcmFjdGVyIG9iamVjdCkgdG8gYSBQT1NJWGN0IGNvbHVtbi4NCkZvciB0aGlzIHdlJ2xsIHBpY2sgYSBmdW5jdGlvbiBmcm9tIGx1YnJpZGF0ZSB0aGF0IG1hdGNoZXMgdGhlIGRhdGUgZm9ybWF0dGluZw0Kd2UgZ290Lg0KDQpgYGB7cn0NCmNvdXMxMl9kdF90YmwgPC0gY291czEyX3RibCB8PiANCiAgbXV0YXRlKGdwc19kdCA9IGRteV9obShHUFNUaW1lKSkNCg0KaGVhZChjb3VzMTJfZHRfdGJsKSANCmBgYA0KDQpcDQoNCkhvdyBtYW55IHJvd3MgYW5kIGNvbHVtbnM/DQoNCmBgYHtyfQ0KZGltKGNvdXMxMl9kdF90YmwpDQpgYGANCg0KXA0KDQojIyBEdXBsaWNhdGUgVGltZSBTdGFtcHMNCg0KRmlyc3Qgd2UgaGF2ZSB0byBkZWFsIHdpdGggZHVwbGljYXRlIHRpbWVzdGFtcHMsIHdoaWNoIHdpbGwgY2F1c2UgYSBwcm9ibGVtIHdoZW4NCndlIGNvbnZlcnQgdGhlIGRhdGEgZnJhbWUgdG8gYSBtb3ZlIG9iamVjdC4NCg0KSG93IG1hbnkgZHVwbGljYXRlIHRpbWUgc3RhbXBzIGFyZSB0aGVyZT8NCg0KYGBge3J9DQpjb3VzMTJfZHRfdGJsIHw+IA0KICBncm91cF9ieShncHNfZHQpIHw+IA0KICBzdW1tYXJpemUobnVtX3B0cyA9IG4oKSkgfD4gDQogIGZpbHRlcihudW1fcHRzID4gMSkgfD4gDQogIGRpbSgpDQpgYGANCg0KXA0KDQpUaGlzIHRlbGxzIHVzIHRoZXJlIGFyZSA1OCB0aW1lIHN0YW1wcyB0aGF0IGhhdmUgbW9yZSB0aGFuIG9uZSByb3cuIFBlcmhhcHMNCnRoZXJlIHdlcmUgdHdvIGxvY2F0aW9ucyB3aXRoaW4gdGhlIHNhbWUgbWludXRlICh0aGUgR1BTIHRpbWUgc3RhbXBzIGRvbid0DQppbmNsdWRlIHNlY29uZHMpLiBEdXBsaWNhdGUgbG9jYXRpb25zIGFyZSBub3QgdGhhdCB1bmNvbW1vbiBpbiBtYW55IGFuaW1hbA0KdHJhY2tpbmcgZGF0YXNldHMuIElmIHlvdSB3YW50IHRvIGV4cGxvcmUgZnVydGhlciB5b3UgY291bGQgb3BlbiB0aGUgQ1NWIGZpbGUgaW4NCkV4Y2VsIGFuZCBleGFtaW5lIHRoZW0sIGJ1dCB3ZSB3aWxsIGp1c3QgZGVsZXRlIHRoZW0uDQoNCkFuIGVhc3kgd2F5IHRvIGdldCByaWQgb2YgZHVwbGljYXRlcyBpcyBieSBncm91cGluZzoNCg0KYGBge3J9DQpjb3VzMTJfZHRfbm9kdXBzX3RibCA8LSBjb3VzMTJfZHRfdGJsIHw+IA0KICBncm91cF9ieShncHNfZHQpIHw+IA0KICBzdW1tYXJpemUobnVtX3B0cyA9IG4oKSwgDQogICAgICAgICAgICBHcHNEZXNjcmlwdGlvbiA9IGZpcnN0KEdwc0Rlc2NyaXB0aW9uKSwgDQogICAgICAgICAgICBsb24gPSBmaXJzdChsb24pLCANCiAgICAgICAgICAgIGxhdCA9IGZpcnN0KGxhdCkpIHw+IA0KICB1bmdyb3VwKCkNCg0KaGVhZChjb3VzMTJfZHRfbm9kdXBzX3RibCkNCmBgYA0KDQpcDQoNCkNvbXBhcmUgdGhlIG51bWJlciBvZiByb3dzIGJlZm9yZSBhbmQgYWZ0ZXIgcmVtb3ZpbmcgZHVwbGljYXRlIHRpbWVzdGFtcHM6DQoNCmBgYHtyfQ0KZGltKGNvdXMxMl9kdF90YmwpDQpkaW0oY291czEyX2R0X25vZHVwc190YmwpICANCmBgYA0KDQojIyBDb252ZXJ0IHRoZSBkYXRhIGZyYW1lIHRvIGEgbW92ZSBvYmplY3QNCg0KYGBge3J9DQpjb3VzMTJfbW92IDwtIG1vdmVWaXM6OmRmMm1vdmUoY291czEyX2R0X25vZHVwc190YmwsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2ogPSAiK2luaXQ9ZXBzZzo0MzI2ICtwcm9qPWxvbmdsYXQgK2RhdHVtPVdHUzg0ICtub19kZWZzICtlbGxwcz1XR1M4NCArdG93Z3M4ND0wLDAsMCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPSAibG9uIiwgeSA9ICJsYXQiLCB0aW1lID0gImdwc19kdCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhY2tfaWQgPSAiR3BzRGVzY3JpcHRpb24iKQ0KYGBgDQoNCldlIGNhbiBpZ25vcmUgdGhlIHdhcm5pbmcgYWJvdXQgdGhlIGRhdHVtLiBtb3ZlVmlzIGlzIHN0aWxsIHVzaW5nIGEgcmdkYWwgd2hpY2gNCmlzIHRoZSB1bmRlcmx5aW5nIHByb2JsZW0uDQoNClNlZSB3aGF0IHdlIGdvdDoNCg0KYGBge3J9DQpjb3VzMTJfbW92DQpkaW0oY291czEyX21vdikNCmBgYA0KDQpcDQoNCiMjIFNlbGVjdCBhIHRpbWUgaW50ZXJ2YWwgZm9yIGVhY2ggZnJhbWUNCg0KVG8gbWFrZSB0aGUgYW5pbWF0aW9uLCB3ZSdyZSBnb2luZyB0byBoYXZlIHRvIHNlbGVjdCB0aGUgbnVtYmVyIG9mDQptaW51dGVzL3NlY29uZHMgcmVwcmVzZW50ZWQgYnkgZWFjaCBmcmFtZS4gV2UnbGwgbmVlZCB0aGlzIHdoZW4gd2UgdXNlIHRoZQ0KbW92ZVZpcyBmdW5jdGlvbiB0aGF0IHdpbGwgJ2FsaWduJyB0aGUgZGF0YXNldCB0byB0aGlzIHNhbXBsaW5nIGludGVydmFsLA0KaW50ZXJwb2xhdGluZyBsb2NhdGlvbnMgYXMgbmVlZGVkLiBUaGlzIGRhdGFzZXQgb25seSBoYXMgdGhlIGxvY2F0aW9ucyBmb3Igb25lDQpjb3lvdGUsIGJ1dCBpZiBpdCBjb250YWluZWQgbXVsdGlwbGUgY295b3RlcyB0aGVpciBsb2NhdGlvbnMgd291bGQgYWxsIGJlDQppbnRlcnBvbGF0ZWQgdG8gdGhlIHNhbWUgZnJhbWUgaW50ZXJ2YWwuDQoNClRoZXJlIGFyZSBhbHdheXMgYSBmZXcgZ2FwcyBpbiBtb3ZlbWVudCBkYXRhc2V0cywgYnV0IGluIGdlbmVyYWwgaXQgbWFrZXMgc2Vuc2UNCnRvIHVzZSB0aGUgZG9taW5hbnQgc2FtcGxpbmcgaW50ZXJ2YWw6DQoNCmBgYHtyfQ0KdGltZUxhZyhjb3VzMTJfbW92LCB1bml0ID0gInNlY3MiKSB8PiB0YWJsZSgpDQoNCnRpbWVMYWcoY291czEyX21vdiwgdW5pdCA9ICJzZWNzIikgfD4gbWVkaWFuKCkNCmBgYA0KDQpcDQoNCjkwMCBzZWNvbmRzIGlzIDE1IG1pbnV0ZXMuIEFsaWduIGFsbCB0aGUgc2FtcGxlcyB0byBiZSA5MDBzIGFwYXJ0Og0KDQpgYGB7cn0NCmNvdXMxMl85MDBzX21vdiA8LSBhbGlnbl9tb3ZlKGNvdXMxMl9tb3YsIHJlcyA9IDkwMCwgdW5pdCA9ICJzZWNzIikNCg0KZGltKGNvdXMxMl85MDBzX21vdikNCmBgYA0KDQpcDQoNCiMjIFNlbGVjdCBhIGJhc2VtYXAgaW1hZ2UNCg0KbW92ZVZpcyBhbGxvd3MgeW91IHRvIHByb3ZpZGUgeW91ciBvd24gYmFzZW1hcCBpbWFnZSAoZWcuLCBhIEdlb1RJRkYpLCBvciB5b3UNCmNhbiBncmFiIG9uZSBvbi10aGUtZmx5IGZyb20gT3BlbiBTdHJlZXQgTWFwIG9yIE1hcEJveC4gRm9yIHVyYmFuIGNveW90ZXMsIGEgc2F0ZWxsaXRlDQppbWFnZSB3b3VsZCBsb29rIG5pY2UgKHNlZSB5b3UgY2FuIHNlZSB0aGUgcGFya3MgYW5kIGdyZWVud2F5cyksIGJ1dCBmb3INCmV4cGVkaWVuY3kgd2UnbGwgdXNlIE9TTToNCg0KYGBge3J9DQpnZXRfbWFwdHlwZXMobWFwX3NlcnZpY2UgPSAnb3NtJykNCmBgYA0KDQpcDQoNCiMjIENyZWF0ZSBGcmFtZXMNCg0KQ3JlYXRpbmcgZnJhbWVzIHdpbGwgZ28gYSBiaXQgZmFzdGVyIGlmIHdlIHNldCB1cCBwYXJhbGxlbCBwcm9jZXNzaW5nOg0KDQpgYGB7cn0NCnVzZV9tdWx0aWNvcmUobl9jb3JlcyA9IE5VTEwsIHZlcmJvc2UgPSBUUlVFKQ0KYGBgDQoNClwNCg0KV2UgY2FuIGFsc28gdGVsbCBpdCB0byBzYXZlIHRoZSBmcmFtZXMgdG8gZGlzayAodG8gc2F2ZSBvbiBtZW1vcnkpDQoNCmBgYHtyfQ0KdXNlX2Rpc2soDQogIGZyYW1lc190b19kaXNrID0gVFJVRSwNCiAgZGlyX2ZyYW1lcyA9IHBhc3RlMCh0ZW1wZGlyKCksICIvbW92ZVZpcyIpLA0KICBuX21lbW9yeV9mcmFtZXMgPSAxMDAsDQogIHZlcmJvc2UgPSBUUlVFDQopDQpgYGANCg0KXA0KDQpXZSBjcmVhdGUgdGhlIGZyYW1lcyB3aXRoIGBmcmFtZXNfc3BhdGlhbCgpYC4gVGhlcmUgYXJlIGxvdHMgb2YgYXJndW1lbnRzIGluDQpgZnJhbWVzX3NwYXRpYWwoKWAsIGFuZCBleHRyYSBmdW5jdGlvbnMgeW91IGNhbiB0YWNrIG9uIHRvIGNyZWF0ZSB0aGUgZnJhbWVzLg0KR2V0dGluZyB0aGUgcmlnaHQgbG9vayBpcyBnb2luZyB0byBiZSBhIG1hdHRlciBhbmQgdHJpYWwgYW5kIGVycm9yLiBtYWtpbmcNCmZyYW1lcyBpcyBwcmV0dHkgZmFzdCwgYnV0IHlvdSBjYW4gdXNlIGBzdWJzZXRfbW92ZSgpYCB0byB3b3JrIG9uIGp1c3QgYSBzdWJzZXQgb2YNCnRoZSBsb2NhdGlvbnMuDQoNCk9uZSB0aGluZyB0byBub3RlIGlzIHRoYXQgd2UgZG9uJ3Qgc3BlY2lmeSB0aGUgZnJhbWUgc2l6ZSBvciByZXNvbHV0aW9uIHdoZW4NCndlJ3JlIGNvbnN0cnVjdGluZyB0aGUgZnJhbWVzLiBVbmRlciB0aGUgaG9vZCwgZWFjaCBmcmFtZSBpcyBhbiB1bnJlbmRlcmVkDQpnZ3Bsb3Qgb2JqZWN0LiBXZSdsbCAgc3BlY2lmeSB0aGUgb3V0cHV0IHJlc29sdXRpb24gaW4gdGhlIGxhc3Qgc3RlcCB3aGVuIHdlDQonc3RpdGNoJyB0aGUgZnJhbWVzIGludG8gYW4gYW5pbWF0aW9uIGZpbGUuDQoNCk9uIG15IG1hY2hpbmUgZ2VuZXJhdGluZyAxODk1IGZyYW1lcyB0b29rIH40IG1pbnV0ZXM6DQoNCmBgYHtyfQ0KZnJhbWVzIDwtIGZyYW1lc19zcGF0aWFsKGNvdXMxMl85MDBzX21vdiwgcGF0aF9jb2xvdXJzID0gYygicmVkIiksDQogICAgICAgICAgICAgICAgICAgICAgIG1hcF9zZXJ2aWNlID0gIm9zbSIsIG1hcF90eXBlID0gYygic3RyZWV0cyIsICJ3YXRlcmNvbG9yIilbMV0sIGFscGhhID0gMC41KSB8PiAgDQphZGRfbGFiZWxzKHggPSAiTG9uZyIsIHkgPSAiTGF0IikgfD4gIA0KYWRkX25vcnRoYXJyb3coKSB8PiANCmFkZF9zY2FsZWJhcigpIHw+ICANCmFkZF90aW1lc3RhbXBzKHR5cGUgPSAibGFiZWwiKSAlPiUgYWRkX3Byb2dyZXNzKCkNCmBgYA0KDQpgZnJhbWVzYCBpcyBhIGxpc3QuIExldCdzIHNlZSBob3cgbWFueSBmcmFtZXMgaXQgbWFkZToNCg0KYGBge3J9DQpsZW5ndGgoZnJhbWVzKQ0KYGBgDQoNClwNCg0KRWFjaCBlbGVtZW50IG9mIGBmcmFtZXNgIGlzIGEgZ2dwbG90IG9iamVjdDoNCg0KYGBge3J9DQpjbGFzcyhmcmFtZXNbWzEwMF1dKQ0KYGBgDQoNClwNCg0KTGV0J3MgcHJldmlldyBvbmUgb2YgdGhlbToNCg0KYGBge3J9DQpmcmFtZXNbWzEwMF1dIA0KYGBgDQoNClwNCg0KIyMgQ3JlYXRlIHRoZSBhbmltYXRpb24NCg0KVGhlIGZpbmFsIHN0ZXAgaXMgdG8gJ3N0aXRjaCcgYWxsIHRoZSBmcmFtZXMgdG9nZXRoZXIgaW50byBhbiBhbmltYXRpb24uIFVuZGVyDQp0aGUgaG9vZCwgbW92ZVZpcyB1c2VzIGZmbXBlZy4gVGhlcmUgYXJlIGEgbnVtYmVyIG9mIG91dHB1dCBmaWxlIGZvcm1hdHMNCnN1cHBvcnRlZC4gSSB3b3VsZCByZWNvbW1lbmQgYW5pbWF0ZWQgZ2lmcyAod2hpY2ggd29yayByZWxpYWJseSBpbiBQb3dlclBvaW50LA0KR29vZ2xlIFNsaWRlcywgYW5kIEhUTUwgcGFnZXMpIG9yIG1wNC4NCg0KVGhpcyBpcyBwcm9iYWJseSB0aGUgbG9uZ2VzdCBzdGVwIGluIHRoZSBwcm9jZXNzLiBPbiBteSBsYXB0b3AsIHRoaXMgc3RlcCB0b29rDQp+MzAgbWludXRlcyB0byBtYWtlIGEgR0lGLCBhbmQgMjcgbWludXRlcyB0byBtYWtlIGEgbXA0LiBZb3UgY2FuJ3QgcmVhbGx5IHNwZWVkDQppdCB1cCBiZWNhdXNlIHZpZGVvIGVuY29kaW5nIGluIGdlbmVyYWwgY2FuIG5vdCBiZSBwYXJhbGxlbGl6ZWQuDQoNCmBgYHtyfQ0KYW5pbWF0ZV9mcmFtZXMoZnJhbWVzLCBvdXRfZmlsZSA9ICJjb3VzMTJfdjEuZ2lmIiwNCiAgICAgICAgICAgICAgIGZwcyA9IDI1LA0KICAgICAgICAgICAgICAgd2lkdGggPSA3MDAsDQogICAgICAgICAgICAgICBoZWlnaHQgPSA3MDAsDQogICAgICAgICAgICAgICByZXMgPSAxMDAsDQogICAgICAgICAgICAgICBvdmVyd3JpdGUgPSBGQUxTRSkNCmBgYA0KDQpcDQoNCldoYXQgZGlkIHdlIGdldD8NCg0KIVtdKC4vY291czEyX3YxLmdpZikNCg0KIyMgRU5EDQoNClwNCg==